/*
 * $Log: DominantRegionLayer.java,v $ Revision 1.2 2006/11/05 13:57:59 wetzker
 * more merging - CLang + MF + some states still missing
 * 
 * Revision 1.1.28.1 2006/07/26 15:28:26 endert Formatted Code
 * 
 * Revision 1.1 2006/03/13 07:37:39 axle moved from ROOT/src for Maven 2
 * Buildsystem and Team/Soccerscope separation
 * 
 * Revision 1.4 2006/03/07 12:46:20 endert Merge after refactoring: - New
 * State-Interface: One Method for each action type - Action Bean removed -
 * ActionFactorys for helper methods, States calculate main-Action themselves -
 * formatted code - added State-List in Monitor (Context-Menu of Player) - other
 * smaller stuff
 * 
 * Revision 1.3.2.1 2006/02/25 22:07:08 wetzker formated
 * 
 * Revision 1.3 2006/02/22 16:05:02 endert This is done: - Rebound SoccerScope
 * to Agents (can be added or removed in config-file) - Situations now all
 * extend abstract class Situation - Refactorings: ActionBean _ removed from
 * Attributes - SoccerScope will automatically connect to host given in
 * Start-Call - Trainer uses the same host as soccerscope - Removed unused stuff
 * (old confs, bat, etc.) - Code formatted (good luck when merging) - organized
 * imports - all that i forgot to mention here
 * 
 * Revision 1.2 2005/06/19 19:44:37 till added parameter teamSide to constructor
 * of soccerscope.model.Player
 * 
 * Revision 1.1 2005/04/20 10:38:39 endert Fresh Team Repository installation
 * from /home/e/endert/Robocup to here.
 * 
 * Revision 1.3 2005/02/06 07:20:09 maketi Added soccerscope with connection to
 * our world model
 * 
 * Revision 1.11 2003/03/24 05:58:44 koji Jimi,Gif89Encoder����
 * 
 * Revision 1.10 2002/12/02 08:48:35 koji ControlRegionLayer�ɲ�
 * 
 * Revision 1.9 2002/10/18 07:21:58 koji
 * ͥ*�ΰ�η׻���ˡ���ڤ��ؤ����ΰ襫����ȡ��ץ쥤�䡼j����֤�ɽ��
 * 
 * Revision 1.8 2002/10/10 09:43:40 koji ͥ*�ΰ�η׻��β���
 * (Direction-Distance Table)
 * 
 * Revision 1.7 2002/10/04 10:39:23 koji
 * Tuple,Point,Vector�Υѥå������ѹ���Geometry,Circle,Line,Rectangle�ɲ�
 * 
 * Revision 1.6 2002/09/12 17:07:07 koji
 * HeteroParam�d�ä��ѹ���Hetro->Hetero
 * 
 * Revision 1.5 2002/09/12 15:28:31 koji
 * ������%����?���ɲ�,ColorTool��Color2�����
 * 
 * Revision 1.4 2002/09/05 10:21:25 taku-ako �إƥ�ץ쥤�䡼���б�
 * 
 * Revision 1.3 2002/09/02 07:06:28 taku-ako
 * Geometry��Ϣ�Υ饤�֥����.
 * 
 * Revision 1.2 2002/03/01 14:24:41 koji *** empty log message ***
 * 
 * Revision 1.1.1.1 2002/03/01 14:12:53 koji CVS�����
 * 
 */

package soccerscope.view.layer;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import soccerscope.model.ClientScene;
import soccerscope.model.Param;
import soccerscope.model.Player;
import soccerscope.model.Team;
import soccerscope.util.Color2;
import soccerscope.util.geom.Circle2f;
import soccerscope.util.geom.Geometry;
import soccerscope.util.geom.Line2f;
import soccerscope.util.geom.Point2f;
import soccerscope.util.geom.Tuple2f;
import soccerscope.view.FieldPane;

public class DominantRegionLayer
    extends Layer
    implements ActionListener, ItemListener {

  public DominantRegionLayer(
      FieldPane fieldPane,
      boolean enable) {

    super(fieldPane, enable);
    this.init();
  }

  @Override
  public String getLayerName() {

    return "DominantRegion";
  }

  private JTextField gridxText;

  private JTextField gridyText;

  private JComboBox  methodBox;

  private JCheckBox  timeCheck;

  private JCheckBox  countCheck;

  private JTextField countText;

  @Override
  public JPanel getConfigPanel() {

    JLabel gridxLabel = new JLabel("Grid X");
    JLabel gridyLabel = new JLabel("Grid Y");
    this.gridxText = new JTextField(String.valueOf(this.gridx), 4);
    this.gridyText = new JTextField(String.valueOf(this.gridy), 4);
    JLabel methodLabel = new JLabel("Method");
    String str[] = { "Simple", "Simple+Est", "DDT", "DDT+Est" };
    this.methodBox = new JComboBox(str);
    this.methodBox.setSelectedIndex(3);
    this.timeCheck = new JCheckBox("Draw Time");
    this.countCheck = new JCheckBox("Count Region");
    this.countText = new JTextField(String.valueOf(this.countR), 4);
    JButton apply = new JButton("Apply");
    this.gridxText.addActionListener(this);
    this.gridyText.addActionListener(this);
    this.methodBox.addItemListener(this);
    this.timeCheck.addItemListener(this);
    this.countCheck.addItemListener(this);
    this.countText.addActionListener(this);
    apply.addActionListener(this);

    JPanel p = new JPanel();
    p.setLayout(new GridBagLayout());
    GridBagConstraints gbc1 = new GridBagConstraints();
    GridBagConstraints gbc2 = new GridBagConstraints();
    gbc1.anchor = GridBagConstraints.NORTHWEST;
    gbc1.ipadx = 5;
    gbc1.ipady = 5;
    gbc1.gridx = 0;
    gbc1.weightx = 0.0;
    gbc2.anchor = GridBagConstraints.NORTHWEST;
    gbc2.gridx = 1;
    gbc2.weightx = 1.0;

    p.add(gridxLabel, gbc1);
    p.add(gridyLabel, gbc1);
    p.add(methodLabel, gbc1);

    p.add(this.gridxText, gbc2);
    p.add(this.gridyText, gbc2);
    p.add(this.methodBox, gbc2);

    gbc1.weighty = 1.0;
    gbc1.gridwidth = 2;
    p.add(this.timeCheck, gbc1);
    p.add(this.countCheck, gbc1);
    p.add(this.countText, gbc1);
    p.add(apply, gbc1);

    return p;
  }

  public void actionPerformed(ActionEvent ae) {

    try {
      this.gridx = Float.parseFloat(this.gridxText.getText());
      this.gridy = Float.parseFloat(this.gridyText.getText());
      this.countR = Float.parseFloat(this.countText.getText());
      this.init();
      this.fieldPane.repaint();
    }
    catch (NumberFormatException nfe) {
    }
  }

  public void itemStateChanged(ItemEvent ie) {

    if (ie.getSource() == this.methodBox) {
      this.method = this.methodBox.getSelectedIndex();
      String str[] = { "Simple", "Simple+Est", "DDT", "DDT+Est" };
      System.out.println(str[this.method]);
    }
    else if (ie.getSource() == this.timeCheck) {
      this.drawTime = this.timeCheck.isSelected();
    }
    else if (ie.getSource() == this.countCheck) {
      this.countRegion = this.countCheck.isSelected();
    }
  }

  private int MAX_TIME = 110;

  private int MAX_TURN = 7;

  private void init() {

    this.maxx = (int) (Param.PITCH_LENGTH / this.gridx);
    this.maxy = (int) (Param.PITCH_WIDTH / this.gridy);
    this.field = new Region[this.maxy][this.maxx];
    for (int y = 0; y < this.maxy; y++) {
      this.field[y] = new Region[this.maxx];
      for (int x = 0; x < this.maxx; x++) {
        this.field[y][x] = new Region();
      }
    }
    this.DD = new float[this.MAX_TURN][Param.MAX_PLAYER * 2][this.MAX_TIME];
    for (int i = 0; i < this.MAX_TURN; i++) {
      this.DD[i] = new float[Param.MAX_PLAYER * 2][this.MAX_TIME];
      for (int j = 0; j < Param.MAX_PLAYER * 2; j++) {
        this.DD[i][j] = new float[this.MAX_TIME];
      }
    }
  }

  // private float gridx = 4.0f;
  // private float gridy = 4.0f;
  // private float gridx = 2.0f;
  // private float gridy = 2.0f;
  private float   gridx       = 1.0f;

  private float   gridy       = 1.0f;

  // private float gridx = 0.5f;
  // private float gridy = 0.5f;
  // private float gridx = 0.25f;
  // private float gridy = 0.25f;
  private Region  field[][];

  private int     maxx;

  private int     maxy;

  private Point2f target      = new Point2f();

  private int     method      = 3;

  private boolean drawTime    = false;

  private boolean countRegion = false;

  private float   countR      = 5.0f;

  @Override
  public void draw(Graphics g) {

    if (!this.enable) {
      return;
    }

    long time, diff;

    time = System.currentTimeMillis();
    this.initRegion();
    diff = System.currentTimeMillis();
    System.out.println("init: " + (diff - time));

    time = diff;
    switch (this.method) {
      case 0:
        this.calcRegionBySimpleMethod();
        break;
      case 1:
        this.calcRegionBySimpleMethodWithEstimation();
        break;
      case 2:
        this.calcRegionByDDT();
        break;
      case 3:
      default:
        this.calcRegionByDDTWithEstimation();
        break;
    }
    diff = System.currentTimeMillis();
    System.out.println("calc: " + (diff - time));

    time = diff;
    this.drawRegion(g);
    diff = System.currentTimeMillis();
    System.out.println("draw: " + (diff - time));
  }

  private void drawRegion(Graphics g) {

    Color drawColor;
    ClientScene clientScene = this.fieldPane.getScene();
    ArrayList countRegionList = new ArrayList();
    int cleft, cright, cmid;
    cleft = 0;
    cright = 0;
    cmid = 0;
    if (this.countRegion) {
      for (int i = 0; i < Param.MAX_PLAYER * 2; i++) {
        if (clientScene.player[i].isEnable() && this.fieldPane.isSelected(i)) {
          countRegionList.add(new Circle2f(clientScene.player[i].pos,
              this.countR));
        }
      }
    }

    Tuple2f size = new Tuple2f(this.gridx, this.gridy);
    for (int y = 0; y < this.maxy; y++) {
      for (int x = 0; x < this.maxx; x++) {
        this.target.set(-Param.PITCH_HALF_LENGTH + x * this.gridx,
            -Param.PITCH_HALF_WIDTH + y * this.gridy);
        if (this.field[y][x].idx == Region.UNKNOWN || this.field[y][x].time == Region.UNKNOWN) {
          continue;
        }
        if (this.drawTime) {
          g.setColor(Color.darkGray);
          this.drawString(g, Integer.toString(this.field[y][x].time),
              this.target);
        }
        if (this.field[y][x].idx2 != Region.UNKNOWN) {
          g.setColor(Color2.mix(Color2.mix(
              clientScene.player[this.field[y][x].idx].getColor(),
              clientScene.player[this.field[y][x].idx2].getColor(), 1, 1),
              Color.darkGray, 10, this.field[y][x].time));
        }
        else {
          g.setColor(Color2.mix(clientScene.player[this.field[y][x].idx]
              .getColor(), Color.darkGray, 10, this.field[y][x].time));
        }
        this.fillRect(g, this.target, size);
        if (this.countRegion) {
          Iterator it = countRegionList.iterator();
          count: {
            while (it.hasNext()) {
              if (((Circle2f) it.next()).contains(this.target)) {
                if (this.field[y][x].idx2 != Region.UNKNOWN) {
                  cmid++;
                }
                else if (this.field[y][x].idx < Param.MAX_PLAYER) {
                  cleft++;
                }
                else {
                  cright++;
                }
                break count;
              }
            }
          }
        }
      }
    }
    if (this.countRegion) {
      System.out.println("left: " + cleft);
      System.out.println("mid: " + cmid);
      System.out.println("right: " + cright);
    }
  }

  class Region {

    public final static int UNKNOWN = -1;

    public int              idx;

    public int              time;

    public int              idx2;

    public Region() {

      this.idx = Region.UNKNOWN;
      this.time = Region.UNKNOWN;
      this.idx2 = Region.UNKNOWN;
    }
  }

  // ��������롣time�ν���ͤ�,��Υ���Ǿ���j�꤬����¤��������+10������
  private void initRegion() {

    ClientScene clientScene = this.fieldPane.getScene();
    float mindist, dist;
    for (int y = 0; y < this.maxy; y++) {
      for (int x = 0; x < this.maxx; x++) {
        this.field[y][x].idx = Region.UNKNOWN;
        this.field[y][x].time = Region.UNKNOWN;
        this.target.set(-Param.PITCH_HALF_LENGTH + x * this.gridx,
            -Param.PITCH_HALF_WIDTH + y * this.gridy);
        mindist = 1000.0f;
        for (int i = 0; i < Param.MAX_PLAYER * 2; i++) {
          if (!clientScene.player[i].isEnable()) {
            continue;
          }
          dist = clientScene.player[i].pos.dist(this.target);
          if (dist < mindist) {
            this.field[y][x].idx = i;
            this.field[y][x].idx2 = Region.UNKNOWN;
            this.field[y][x].time = clientScene.player[i]
                .estimateMinTimeByDistance(dist, clientScene.player[i]
                    .getDashAccelerationMax()) + 10;
            mindist = dist;
          }
        }
      }
    }
  }

  private Player dPlayer = new Player(Team.NEUTRAL);

  // ���Ѥ���ˡ�Ƿ׻����롣
  // ���٤Ƥ�j��ˤ�������ɸ���$�ã����ޤǤˤ�������֤�׻�����
  private void calcRegionBySimpleMethod() {

    ClientScene clientScene = this.fieldPane.getScene();
    for (int y = 0; y < this.maxy; y++) {
      for (int x = 0; x < this.maxx; x++) {
        this.target.set(-Param.PITCH_HALF_LENGTH + x * this.gridx,
            -Param.PITCH_HALF_WIDTH + y * this.gridy);

        for (int i = 0; i < Param.MAX_PLAYER * 2; i++) {
          if (!clientScene.player[i].isEnable()) {
            continue;
          }

          this.dPlayer.setPlayer(clientScene.player[i]);
          int time = this.turnTo(this.dPlayer, this.target);
          time += this.dashTo(this.dPlayer, this.target);
          if (this.field[y][x].time == Region.UNKNOWN || this.field[y][x].time > time) {
            this.field[y][x].time = time;
            this.field[y][x].idx = i;
            this.field[y][x].idx2 = Region.UNKNOWN;
          }
          else if (this.field[y][x].time == time) {
            this.field[y][x].idx2 = i;
          }
        }
      }
    }
  }

  private void calcRegionBySimpleMethodWithEstimation() {

    ClientScene clientScene = this.fieldPane.getScene();
    for (int y = 0; y < this.maxy; y++) {
      for (int x = 0; x < this.maxx; x++) {
        this.target.set(-Param.PITCH_HALF_LENGTH + x * this.gridx,
            -Param.PITCH_HALF_WIDTH + y * this.gridy);

        for (int i = 0; i < Param.MAX_PLAYER * 2; i++) {
          if (!clientScene.player[i].isEnable()) {
            continue;
          }

          float dist = clientScene.player[i].pos.dist(this.target);
          float acc = clientScene.player[i].getDashAccelerationMax();
          float minTime = clientScene.player[i].estimateMinTimeByDistance(dist,
              acc);
          if (minTime > this.field[y][x].time) {
            continue;
          }

          this.dPlayer.setPlayer(clientScene.player[i]);
          int time = this.turnTo(this.dPlayer, this.target);
          time += this.dashTo(this.dPlayer, this.target);
          if (this.field[y][x].time == Region.UNKNOWN || this.field[y][x].time > time) {
            this.field[y][x].time = time;
            this.field[y][x].idx = i;
            this.field[y][x].idx2 = Region.UNKNOWN;
          }
          else if (this.field[y][x].time == time) {
            this.field[y][x].idx2 = i;
          }
        }
      }
    }
  }

  private int turnTo(Player p,
      Point2f target) {

    int t = 0;
    Circle2f cir = new Circle2f(target, p.getKickable());
    while (true) {
      if (p.pos.dist(target) <= p.getKickable()) {
        break;
      }

      Line2f pline = p.getLine();
      float angle = p.getAngleFromBody(target);
      if (cir.intersect(pline) && Math.abs(angle) <= 90) {
        break;
      }
      float turnAngle = p.getTurnAngle();
      p.step();
      if (Math.abs(angle) <= turnAngle) {
        p.angle = p.pos.dir(target);
      }
      else if (angle > 0) {
        p.angle = Geometry.normalizeAngle(p.angle + turnAngle);
      }
      else {
        p.angle = Geometry.normalizeAngle(p.angle - turnAngle);
      }
      t++;
    }
    return t;
  }

  private int dashTo(Player p,
      Point2f target) {

    if (p.pos.dist(target) <= p.getKickable()) {
      return 0;
    }

    Circle2f cir = new Circle2f(target, p.getKickable());
    Line2f pline = p.getLine();
    Point2f pos[] = cir.intersection(pline);
    float dist;
    switch (pos.length) {
      case 2:
        dist = Math.min(p.pos.dist(pos[0]), p.pos.dist(pos[1]));
        break;
      case 1:
        dist = p.pos.dist(pos[0]);
        break;
      default:
        dist = p.pos.dist(pline.projectpoint(target));
        break;
    }
    float acc = p.getDashAccelerationMax();
    return p.calcTimeByDistance(dist, acc);
  }

  // Direction-Distance Table��ȤäƷ׻�����
  // ����j�꤬��ɸ���$�ã����Τˤ����������֤ˤ��׻��ξ�ά����
  private void calcRegionByDDT() {

    ClientScene clientScene = this.fieldPane.getScene();
    this.makeDD();
    for (int y = 0; y < this.maxy; y++) {
      for (int x = 0; x < this.maxx; x++) {
        this.target.set(-Param.PITCH_HALF_LENGTH + x * this.gridx,
            -Param.PITCH_HALF_WIDTH + y * this.gridy);

        for (int i = 0; i < Param.MAX_PLAYER * 2; i++) {
          if (!clientScene.player[i].isEnable()) {
            continue;
          }

          this.dPlayer.setPlayer(clientScene.player[i]);
          int time = this.turnTo(this.dPlayer, this.target);
          time += this.dashToUsingDD(this.dPlayer, this.target, time, i);
          if (this.field[y][x].time == Region.UNKNOWN || this.field[y][x].time > time) {
            this.field[y][x].time = time;
            this.field[y][x].idx = i;
            this.field[y][x].idx2 = Region.UNKNOWN;
          }
          else if (this.field[y][x].time == time) {
            this.field[y][x].idx2 = i;
          }
        }
      }
    }
  }

  private void calcRegionByDDTWithEstimation() {

    ClientScene clientScene = this.fieldPane.getScene();
    this.makeDD();
    for (int y = 0; y < this.maxy; y++) {
      for (int x = 0; x < this.maxx; x++) {
        this.target.set(-Param.PITCH_HALF_LENGTH + x * this.gridx,
            -Param.PITCH_HALF_WIDTH + y * this.gridy);

        for (int i = 0; i < Param.MAX_PLAYER * 2; i++) {
          if (!clientScene.player[i].isEnable()) {
            continue;
          }

          float dist = clientScene.player[i].pos.dist(this.target);
          float acc = clientScene.player[i].getDashAccelerationMax();
          float minTime = clientScene.player[i].estimateMinTimeByDistance(dist,
              acc);
          if (minTime > this.field[y][x].time) {
            continue;
          }

          this.dPlayer.setPlayer(clientScene.player[i]);
          int time = this.turnTo(this.dPlayer, this.target);
          time += this.dashToUsingDD(this.dPlayer, this.target, time, i);
          if (this.field[y][x].time == Region.UNKNOWN || this.field[y][x].time > time) {
            this.field[y][x].time = time;
            this.field[y][x].idx = i;
            this.field[y][x].idx2 = Region.UNKNOWN;
          }
          else if (this.field[y][x].time == time) {
            this.field[y][x].idx2 = i;
          }
        }
      }
    }
  }

  private float DD[][][];

  private void makeDD() {

    ClientScene clientScene = this.fieldPane.getScene();
    for (int i = 0; i < this.MAX_TURN; i++) {
      for (int j = 0; j < Param.MAX_PLAYER * 2; j++) {
        if (!clientScene.player[j].isEnable()) {
          continue;
        }
        this.dPlayer.setPlayer(clientScene.player[j]);
        for (int k = 0; k < i; k++) {
          this.dPlayer.step();
        }
        float acc = this.dPlayer.getDashAccelerationMax();
        for (int k = 0; k < this.MAX_TIME; k++) {
          this.DD[i][j][k] = this.dPlayer.calcDistanceAfterNTime(acc, k);
        }
      }
    }
  }

  private int dashToUsingDD(Player p,
      Point2f target,
      int turnTime,
      int idx) {

    if (p.pos.dist(target) <= p.getKickable()) {
      return 0;
    }

    Circle2f cir = new Circle2f(target, p.getKickable());
    Line2f pline = p.getLine();
    Point2f pos[] = cir.intersection(pline);
    float dist;
    switch (pos.length) {
      case 2:
        dist = Math.min(p.pos.dist(pos[0]), p.pos.dist(pos[1]));
        break;
      case 1:
        dist = p.pos.dist(pos[0]);
        break;
      default:
        dist = p.pos.dist(pline.projectpoint(target));
        break;
    }

    int time = this.search(this.DD[turnTime][idx], dist);
    // if (p.unum == 1 && p.side == Team.LEFT_SIDE) {
    // System.out.println("time = " + time);
    // System.out.println(target);
    // System.out.println(dist);
    // System.out.println(cir);
    // System.out.println(pline);
    // System.out.println();
    // }
    return time; // search(DD[turnTime][idx], dist);
  }

  private int search(float[] t,
      float key) {

    int idx = (int) key;
    float tmp = t[idx];
    if (tmp == key) {
      return idx;
    }
    else if (tmp < key) {
      while (tmp < key) {
        idx++;
        tmp = t[idx];
      }
    }
    else {
      while (tmp > key) {
        idx--;
        tmp = t[idx];
      }
      idx++;
    }
    return idx;
  }

}
